package WayPointNav

import (
	"fmt"
	"math"
	"math/rand"
	"time"
	. "utils"

	. "git.oschina.net/yangdao/extlib/data_type"
)

const (
	WayType_Src2Dst = 0
	WayType_Dst2Src = 1
	WayType_TwoWay  = 2

	WayActionType_Run      = 0
	WayActionType_Jump     = 1
	WayActionType_FailDown = 2
)

type WayMap struct {
	Points map[int]*WayPoint
	Links  map[string]*WayLink

	findOpenList  map[int]*WayPoint
	findCloseList map[int]*WayPoint
	findDstPoint  *WayPoint
}

func NewWayMap() *WayMap {
	RetWayMap := &WayMap{}
	RetWayMap.Points = make(map[int]*WayPoint)
	RetWayMap.Links = make(map[string]*WayLink)
	RetWayMap.findCloseList = make(map[int]*WayPoint)
	RetWayMap.findOpenList = make(map[int]*WayPoint)
	return RetWayMap
}

func (this *WayMap) LoadFromConfig(configData IAssociative) {
	//gen link
	links := configData.Get(":links").(*TypMap)
	for linkid, linkarr := range links.Vals {
		newLink := &WayLink{}
		newLink.Map = this
		newLink.InitFromConfig(linkarr.(*TypVector))
		this.Links[linkid.(string)] = newLink
	}
	//gen point
	points := configData.Get(":points").(*TypVector)
	for i := 0; i < points.Count(); i++ {
		curWayPoint := points.Nth(i).(*TypVector)
		newWayPoint := &WayPoint{}
		newWayPoint.Map = this
		newWayPoint.InitFromConfig(curWayPoint)
		this.Points[newWayPoint.ID] = newWayPoint
	}

	for _, link := range this.Links {
		switch link.WayType {
		case 0:
			this.Points[link.StartPointId].Links.Conj(link)
		case 1:
			this.Points[link.EndPointId].Links.Conj(link)
		case 2:
			this.Points[link.StartPointId].Links.Conj(link)
			this.Points[link.EndPointId].Links.Conj(link)
		}
	}
}

func (this *WayMap) GetRamdomPoint() *WayPoint {
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	idx := r.Intn(len(this.Points))
	i := 0
	for _, point := range this.Points {
		if idx >= i {
			return point
		}
		i++
	}
	return nil
}

func (this *WayMap) FindNearPoint(x float64, y float64) (int, float64) {
	dstPosition := Vec2{X: x, Y: y}
	minLength := math.MaxFloat64
	nearPointId := -1
	var nearLength float64 = math.MaxFloat64
	for pointID, point := range this.Points {
		subPoint := point.Position.Sub(&dstPosition)
		length := subPoint.Length()
		if length < minLength {
			minLength = length
			nearPointId = pointID
			nearLength = length
		}
	}
	return nearPointId, nearLength
}

func (this *WayMap) FindLink(pa, pb int) *WayLink {
	for _, link := range this.Links {
		if link.StartPointId == pa && link.EndPointId == pb {
			return link
		}
		if link.StartPointId == pb && link.EndPointId == pa {
			return link
		}
	}
	return nil
}

func (this *WayMap) FindNearLink(x float64, y float64) (*WayLink, *Vec2) {
	startPos := &Vec2{X: x, Y: y}
	var minPos float64 = math.MaxFloat64
	var minLink *WayLink
	var hitPos *Vec2
	for _, link := range this.Links {
		LinkStartPos := this.Points[link.StartPointId].Position
		LinkEndPos := this.Points[link.EndPointId].Position
		LinkSeg := &Segment{PointA: &LinkStartPos, PointB: &LinkEndPos}
		HeroSeg := &Segment{PointA: &Vec2{X: startPos.X, Y: startPos.Y - 1000}, PointB: &Vec2{X: startPos.X, Y: startPos.Y + 1000}}
		isHit, X, Y := LinkSeg.Intersect(HeroSeg)
		if isHit {
			hitPos = &Vec2{X: X, Y: Y}
			dis := math.Abs(startPos.Sub(hitPos).Length())
			if dis < minPos {
				minPos = dis
				minLink = link
			}
		}
	}
	return minLink, hitPos
}

func (this *WayMap) FindPath(startID int, endID int) *TypVector {
	retList := Vector()
	this.findCloseList = make(map[int]*WayPoint)
	this.findOpenList = make(map[int]*WayPoint)
	this.findCloseList[startID] = this.Points[startID]
	this.findDstPoint = this.Points[endID]
	for _, point := range this.Points {
		point.PID = -1
		point.F = 0
		point.G = 0
		point.H = 0
	}

	curPointId := startID
	var isFind bool = false
	for isFind == false {
		//fmt.Println("push id", curPointId)
		this.PushOpenList(curPointId)
		//fmt.Println("push ", this.findOpenList)
		//fmt.Println("close ", this.findCloseList)
		selectPointId := this.SelectPoint(curPointId)
		//fmt.Println("select ", selectPointId)
		if selectPointId == -1 {
			for _, point := range this.findOpenList {
				if point.ID == endID {
					isFind = true
					curPointId = point.ID
					break
				}
				this.PushOpenList(point.ID)
				selectPointId = this.SelectPoint(point.ID)
				if selectPointId != -1 {
					curPointId = selectPointId
					break
				}
			}
		}

		delete(this.findOpenList, selectPointId)
		this.findCloseList[selectPointId] = this.Points[selectPointId]

		if selectPointId == endID {
			isFind = true
		}
		curPointId = selectPointId
	}
	curPathId := endID
	//fmt.Println("find over curpathID:", curPathId)
	maxLoopNum := 0
	for curPathId != -1 {
		maxLoopNum++
		if maxLoopNum > 100 {
			return nil
		}
		//fmt.Println("curPathId", curPathId, "PID", this.Points[curPathId].PID)
		retList.Conj(curPathId)
		//fmt.Println("path conj:", curPathId)
		curPathId = this.Points[curPathId].PID
	}
	retList.Reverse()
	return retList
}

func (this *WayMap) PushOpenList(pointID int) {
	curPoint := this.Points[pointID]
	if curPoint == nil {
		fmt.Println("PushOpenList curPoint is nil pointID", pointID)
	}
	for i := 0; i < curPoint.Links.Count(); i++ {
		curLink := curPoint.Links.Nth(i).(*WayLink)
		otherPoint := curLink.OtherPoint(curPoint.ID)
		if this.findCloseList[otherPoint] != nil {
			continue
		}
		dstPoint := this.Points[otherPoint]
		if this.findOpenList[dstPoint.ID] == nil {
			dstPoint.PID = curPoint.ID
		}
		this.findOpenList[dstPoint.ID] = dstPoint
		subLength := dstPoint.Position.Sub(&curPoint.Position).Length()
		subLength = math.Abs(subLength)
		dstPoint.G += curPoint.G + subLength
		dstPoint.H = this.findDstPoint.Position.Sub(&dstPoint.Position).Length()
		dstPoint.F = dstPoint.G + dstPoint.H

	}
}

func (this *WayMap) SelectPoint(curPointId int) int {
	minF := math.MaxFloat64
	minPoint := -1
	curPoint := this.Points[curPointId]
	for i := 0; i < curPoint.Links.Count(); i++ {
		curLink := curPoint.Links.Nth(i).(*WayLink)
		linkPoint := this.Points[curLink.OtherPoint(curPointId)]

		if this.findCloseList[linkPoint.ID] != nil {
			continue
		}
		if linkPoint.F < minF {
			minF = linkPoint.F
			minPoint = linkPoint.ID
		}
	}
	return minPoint
}

type WayPoint struct {
	ID       int
	Position Vec2
	Links    *TypVector //*WayLink
	F        float64
	G        float64
	H        float64
	PID      int
	Map      *WayMap
}

func (this *WayPoint) InitFromConfig(configData *TypVector) {
	this.ID = configData.Nth(0).(int)
	this.Position.X = configData.Nth(1).(float64)
	this.Position.Y = configData.Nth(2).(float64)
	this.PID = -1
	this.Links = Vector()
}

func (this *WayPoint) GetLink(pointID int) *WayLink {
	for i := 0; i < this.Links.Count(); i++ {
		linkId := this.Links.Nth(i).(*WayLink).ID
		if this.Map.Links[linkId].EndPointId == pointID || this.Map.Links[linkId].StartPointId == pointID {
			return this.Map.Links[linkId]
		}
	}
	return nil
}

type WayLink struct {
	Map           *WayMap
	ID            string
	StartPointId  int
	EndPointId    int
	WayType       int
	S2DActionType int
	D2SActionType int
}

func (this *WayLink) OtherPoint(pointId int) int {
	if this.StartPointId == pointId {
		return this.EndPointId
	} else {
		return this.StartPointId
	}
}

func (this *WayLink) GetActionType(dstPointId int) int {
	if dstPointId == this.EndPointId {
		return this.S2DActionType
	} else {
		return this.D2SActionType
	}
}
func (this *WayLink) GetDir(dstPointId int, wayMap *WayMap) *Vec2 {
	endPoint := wayMap.Points[this.EndPointId]
	startPoint := wayMap.Points[this.StartPointId]
	if dstPointId == this.EndPointId {
		return endPoint.Position.Sub(&startPoint.Position).Normalized()
	} else {
		return startPoint.Position.Sub(&endPoint.Position).Normalized()
	}
}

func (this *WayLink) IsHitLink(posX, posY float64) bool {
	return this.HitLink(posX, posY) != nil
}

func (this *WayLink) HitLink(posX, posY float64) *Vec2 {
	LinkSeg := &Segment{PointA: &this.Map.Points[this.StartPointId].Position, PointB: &this.Map.Points[this.EndPointId].Position}
	HeroSeg := &Segment{PointA: &Vec2{X: posX, Y: posY - 1000}, PointB: &Vec2{X: posX, Y: posY + 1000}}
	isHit, X, Y := LinkSeg.Intersect(HeroSeg)
	if isHit {
		return &Vec2{X: X, Y: Y}
	}
	return nil
}

func (this *WayLink) InitFromConfig(configData *TypVector) {
	this.ID = configData.Nth(0).(string)
	this.StartPointId = configData.Nth(1).(int)
	this.EndPointId = configData.Nth(2).(int)
	this.WayType = configData.Nth(3).(int)
	this.S2DActionType = configData.Nth(4).(int)
	this.D2SActionType = configData.Nth(5).(int)
}
